//------------------------------------------------------------------------------
// test_HITS.c: Test suite for HITS algorithm using GraphBLAS and LAGraph
//------------------------------------------------------------------------------

#include <stdio.h>
#include <acutest.h>
#include "LAGraphX.h"
#include "LAGraph_test.h"

#define LEN 512
char msg[LAGRAPH_MSG_LEN];
char filename[LEN + 1];
LAGraph_Graph G = NULL;

// Utility function to compare vectors with expected results
float difference(GrB_Vector vector, double *expected_result, GrB_Index n)
{
    GrB_Vector diff = NULL;
    float err = 0.0;
    OK(GrB_Vector_new(&diff, GrB_FP32, n));
    for (GrB_Index i = 0; i < n; i++) {
        OK(GrB_Vector_setElement_FP64(diff, expected_result[i], i));
    }
    OK(GrB_eWiseAdd(diff, NULL, NULL, GrB_MINUS_FP32, vector, diff, NULL));
    OK(GrB_apply(diff, NULL, NULL, GrB_ABS_FP32, diff, NULL));
    OK(GrB_reduce(&err, NULL, GrB_MAX_MONOID_FP32, diff, NULL));
    OK(GrB_free(&diff));
    return err;
}

// Test function for a specific graph
void test_HITS_on_graph
(
    const char *graph_file,
    bool directed,
    double *expected_hubs,
    double *expected_authorities
)
{
    GrB_Vector hubs = NULL, authorities = NULL;
    GrB_Matrix A = NULL ;
    int iters = 0 ;
    float tol = 1e-4 ;
    int itermax = 100 ;
    GrB_Index n = 0 ;

    // Load the graph
    printf ("HITS test: %s\n", graph_file) ;
    snprintf (filename, LEN, LG_DATA_DIR "%s", graph_file);
    FILE *f = fopen(filename, "r");
    TEST_CHECK (f != NULL);
    OK (LAGraph_MMRead (&A, f, msg));
    OK (fclose(f));
    OK (GrB_Matrix_nrows (&n, A)) ;
    int kind = directed ? LAGraph_ADJACENCY_DIRECTED : LAGraph_ADJACENCY_UNDIRECTED ;
    OK (LAGraph_New (&G, &A, kind, msg)) ;
    TEST_CHECK (A == NULL) ;    // A has been moved into G->A
    OK (LAGraph_Cached_OutDegree (G, msg)) ;
    if (directed)
    {
        OK (LAGraph_Cached_InDegree (G, msg)) ;
        OK (LAGraph_Cached_AT(G, msg)) ;
    }

    // Run HITS algorithm
    OK (LAGr_HITS (&hubs, &authorities, &iters, G, tol, itermax, msg)) ;
    OK (LAGraph_Vector_Print (hubs, 2, stdout, msg)) ;
    OK (LAGraph_Vector_Print (authorities, 2, stdout, msg)) ;

    // Compare results with expected values
    if (expected_hubs != NULL && expected_authorities != NULL)
    {
        float err_hubs = difference(hubs, expected_hubs, n);
        float err_auth = difference(authorities, expected_authorities, n);
        TEST_CHECK(err_hubs < 1e-4 && err_auth < 1e-4);
    }

    // Clean up
    OK(GrB_free(&hubs));
    OK(GrB_free(&authorities));
    OK(LAGraph_Delete(&G, msg));
}

// Test cases for different graphs
void test_HITS(void)
{
    LAGraph_Init(msg);

    // Test case for Graph 1
    // Define expected hubs and authorities values for the first graph
    double structure_hubs[] ={
        0.13865498151112815,
        0.13865498151112823,
        -7.52772710372611e-17,
        0.19608775534362824,
        -7.52772710372611e-17,
        0.15423823730232433,
        0.3723640443317912
    };

    double structure_authorities[] = {
    0.0884024498169609,
    0.06250997173907646,
    0.3258111125561342,
    0.230383247074376,
    0.23038324707437605,
    -1.5901485815586466e-16,
    0.06250997173907653};

    test_HITS_on_graph ("structure.mtx", true, structure_hubs, structure_authorities) ;

    // Additional test cases can be added here
    double karate_authorities[] = {
        0.07141272880825196,
        0.05342723123552997,
        0.06371906455637479,
        0.042422737124709016,
        0.01526095970620749,
        0.01596691350305964,
        0.015966913503059642,
        0.03434316721905367,
        0.0456819251197503,
        0.020625667749388638,
        0.015260959706207484,
        0.010617891511071214,
        0.01692545079230685,
        0.045494864068056355,
        0.020370345825614276,
        0.02037034582561427,
        0.004748031847301578,
        0.018561637037432098,
        0.020370345825614273,
        0.02971333388643479,
        0.02037034582561427,
        0.018561637037432088,
        0.02037034582561428,
        0.030156497509356398,
        0.011460952230971697,
        0.011893664396281381,
        0.015182734330338388,
        0.026813494117104757,
        0.0263315057779539,
        0.027111539628217676,
        0.035106237976714395,
        0.038375741862956045,
        0.06200184647383098,
        0.0750029421565755
    };

    double karate_hubs[] = {
            0.07141272880825197,
            0.053427231235529976,
            0.06371906455637479,
            0.04242273712470899,
            0.015260959706207477,
            0.01596691350305964,
            0.015966913503059642,
            0.034343167219053644,
            0.045681925119750305,
            0.020625667749388638,
            0.01526095970620748,
            0.010617891511071217,
            0.01692545079230686,
            0.045494864068056355,
            0.020370345825614276,
            0.020370345825614276,
            0.004748031847301572,
            0.018561637037432077,
            0.020370345825614276,
            0.02971333388643479,
            0.020370345825614276,
            0.018561637037432077,
            0.020370345825614276,
            0.030156497509356388,
            0.011460952230971698,
            0.011893664396281383,
            0.015182734330338387,
            0.026813494117104767,
            0.02633150577795389,
            0.02711153962821767,
            0.03510623797671438,
            0.038375741862956045,
            0.062001846473830974,
            0.07500294215657549
        };

    test_HITS_on_graph ("karate.mtx", true, karate_hubs, karate_authorities) ;
    
    double west0067_authorities[] = {
        0.06732949417782225,
        0.018574729659429256,
        0.01857472965942926,
        0.01857472965942926,
        0.018574729659429263,
        0.01632186793713691,
        0.02078477532361338,
        0.00585934344896191,
        0.005859343448961904,
        0.005859343448961908,
        0.005859343448961906,
        0.005021016999031531,
        0.010146952711582115,
        0.010146952711582124,
        0.010146952711582119,
        0.010146952711582119,
        0.00934188807096454,
        0.004684063920168795,
        0.0017875587394001404,
        0.031838153971749474,
        0.007677720669689195,
        0.007677720669689193,
        0.007677720669689198,
        0.007677720669689193,
        0.007670735848683439,
        0.010841557047533654,
        0.010841557047533668,
        0.010841557047533652,
        0.010841557047533656,
        0.00986797158919092,
        0.06988287712194143,
        0.016889037721065245,
        0.01688903772106524,
        0.016889037721065234,
        0.01688903772106524,
        0.016689246436020627,
        0.07502520998403943,
        0.026249126706735713,
        0.026249126706735668,
        0.026249126706735685,
        0.026249126706735678,
        0.024022620819215273,
        0.012673130182575166,
        0.009449644899966868,
        0.009449644899966867,
        0.009449644899966863,
        0.009449644899966867,
        0.008733252034310393,
        0.04586314226355754,
        0.01109388908937864,
        0.01109388908937864,
        0.011093889089378639,
        0.011093889089378646,
        0.010913504446435658,
        0.02538051304910646,
        0.009214716813144334,
        0.009214716813144336,
        0.00921471681314433,
        0.009214716813144331,
        0.008201611896653702,
        0.005149806847527351,
        0.003335596853625951,
        0.0033355968536259548,
        0.003335596853625953,
        0.0033355968536259574,
        0.002725698164004691,
        0.002762597692399429
    };

    double west0067_hubs[] = {
        0.003526088046054016,
        0.003526088046054016,
        0.003526088046054016,
        0.003526088046054016,
        0.020909950936193765,
        0.02090995093619377,
        0.020909950936193765,
        0.020909950936193765,
        0.020245944586658682,
        0.009307374813197,
        0.008463627562768736,
        0.008463627562768737,
        0.008463627562768737,
        0.008463627562768736,
        0.008629875592757835,
        0.023369827672566602,
        0.023369827672566602,
        0.023369827672566602,
        0.023369827672566602,
        0.023168668350737887,
        0.009798738937493466,
        0.00979873893749346,
        0.009798738937493461,
        0.009798738937493461,
        0.046687128336499024,
        0.04668712833649902,
        0.04668712833649902,
        0.04668712833649902,
        0.045889697736916354,
        0.02414744714926618,
        0.028576497121579458,
        0.02857649712157945,
        0.02857649712157945,
        0.028576497121579454,
        0.028044221597879726,
        0.0042956652047794405,
        0.004295665204779443,
        0.004295665204779439,
        0.0042956652047794405,
        0.01745013702794716,
        0.017450137027947163,
        0.01745013702794716,
        0.017450137027947163,
        0.017080820240550767,
        0.00855692644069361,
        0.0026496905128096547,
        0.002649690512809655,
        0.0026496905128096542,
        0.0026496905128096555,
        0.008074664429681208,
        0.008074664429681208,
        0.008074664429681204,
        0.008074664429681206,
        0.007675980312928068,
        0.0032091586092053287,
        0.0003046389467379955,
        0.004849929687076762,
        0.008509108290457435,
        0.0065410638456684465,
        0.014357250780909648,
        0.015443755981969173,
        0.02198766971337126,
        0.009422460296381946,
        0.00907226721772279,
        0.007679287178074854,
        0.007930037691283138,
        0.0027383517860651856
    };

    test_HITS_on_graph ("west0067.mtx", true, west0067_hubs, west0067_authorities) ;

    LAGraph_Finalize(msg);
}

void test_HITS2 (void)
{
    LAGraph_Init (msg) ;
    printf ("\n") ;
    test_HITS_on_graph ("karate.mtx", false, NULL, NULL) ;
    test_HITS_on_graph ("karate_verysparse.mtx", false, NULL, NULL) ;
    test_HITS_on_graph ("diamonds.mtx", false, NULL, NULL) ;
    LAGraph_Finalize (msg);
}

// List of tests to run
TEST_LIST = {
    {"test_HITS", test_HITS},
    {"test_HITS2", test_HITS2},
    {NULL, NULL}
};

